//
// MethodDefinition.cs
//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// (C) 2005 - 2008  Jb Evain
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;

using Mono.Cecil.Cil;

using RVA = System.UInt32;

namespace Mono.Cecil {

	public sealed class MethodDefinition : MethodReference, IMemberDefinition, IGenericContext/*,
		IHasSecurity*/, ICustomAttributeProvider {

		MethodAttributes attributes;
		MethodImplAttributes impl_attrs;
		MethodSemanticsAttributes? sem_attrs;
		//SecurityDeclarationCollection m_secDecls;
		CustomAttributeCollection custom_attributes;

		MethodBody body;
		RVA rva;
		PInvokeInfo pinvoke;
		OverrideCollection overrides;

		public MethodAttributes Attributes {
			get { return attributes; }
			set { attributes = value; }
		}

		public MethodImplAttributes ImplAttributes {
			get { return impl_attrs; }
			set { impl_attrs = value; }
		}

		public MethodSemanticsAttributes SemanticsAttributes {
			get {
				if (sem_attrs.HasValue)
					return sem_attrs.Value;

				if (HasImage) {
					ReadSemantics ();
					return sem_attrs.Value;
				}

				sem_attrs = MethodSemanticsAttributes.None;
				return sem_attrs.Value;
			}
			set { sem_attrs = value; }
		}

		internal void ReadSemantics ()
		{
			var type = DeclaringType;
			if (type == null)
				return;

			var module = type.Module;
			if (module == null)
				return;

			if (!module.HasImage)
				return;

			sem_attrs = module.Read (this, (method, reader) => reader.ReadMethodSemantics (method));
		}

		/*public SecurityDeclarationCollection SecurityDeclarations {
			get {
				if (m_secDecls == null)
					m_secDecls = new SecurityDeclarationCollection (this);

				return m_secDecls;
			}
		}*/

		public bool HasCustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes.Count > 0;

				if (HasImage)
					return DeclaringType.Module.Read (this, (type, reader) => reader.HasCustomAttributes (type));

				return false;
			}
		}

		public CustomAttributeCollection CustomAttributes {
			get {
				if (custom_attributes != null)
					return custom_attributes;

				if (HasImage)
					return custom_attributes = DeclaringType.Module.Read (this, (type, reader) => reader.ReadCustomAttributes (type));

				return custom_attributes = new CustomAttributeCollection ();
			}
		}

		public int RVA {
			get { return (int) rva; }
			internal set { rva = (RVA) value; }
		}


		public bool HasBody {
			get {
				if (body != null)
					return true;

				if (rva == 0)
					return false;

				return (attributes & MethodAttributes.Abstract) == 0 &&
					(attributes & MethodAttributes.PInvokeImpl) == 0 &&
					(impl_attrs & MethodImplAttributes.InternalCall) == 0 &&
					(impl_attrs & MethodImplAttributes.Native) == 0 &&
					(impl_attrs & MethodImplAttributes.Unmanaged) == 0 &&
					(impl_attrs & MethodImplAttributes.Runtime) == 0;
			}
		}

		public MethodBody Body {
			get {
				if (body != null)
					return body;

				if (HasBody && HasImage)
					return body = DeclaringType.Module.Read (this, (method, reader) => reader.ReadMethodBody (method));

				return body = new MethodBody (this);
			}
		}

		public bool HasPInvokeInfo {
			get {
				if (pinvoke != null)
					return true;

				return IsPInvokeImpl;
			}
		}

		public PInvokeInfo PInvokeInfo {
			get {
				if (pinvoke != null)
					return pinvoke;

				if (HasImage && IsPInvokeImpl)
					return pinvoke = DeclaringType.Module.Read (this, (method, reader) => reader.ReadPInvokeInfo (method));

				return null;
			}
			set {
				IsPInvokeImpl = true;
				pinvoke = value;
			}
		}

		public bool HasOverrides {
			get {
				if (overrides != null)
					return overrides.Count > 0;

				if (HasImage)
					return DeclaringType.Module.Read (this, (method, reader) => reader.HasOverrides (method));

				return false;
			}
		}

		public OverrideCollection Overrides {
			get {
				if (overrides != null)
					return overrides;

				if (HasImage)
					return overrides = DeclaringType.Module.Read (this, (method, reader) => reader.ReadOverrides (method));

				return overrides = new OverrideCollection ();
			}
		}

		public override bool HasGenericParameters {
			get {
				if (generic_parameters != null)
					return generic_parameters.Count > 0;

				if (HasImage)
					return DeclaringType.Module.Read (this, (method, reader) => reader.HasGenericParameters (method));

				return false;
			}
		}

		public override GenericParameterCollection GenericParameters {
			get {
				if (generic_parameters != null)
					return generic_parameters;

				if (HasImage)
					return generic_parameters = DeclaringType.Module.Read (this, (method, reader) => reader.ReadGenericParameters (method));

				return generic_parameters = new GenericParameterCollection ();
			}
		}

		#region MethodAttributes

		public bool IsCompilerControlled {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.CompilerControlled; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.CompilerControlled;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.CompilerControlled);
			}
		}

		public bool IsPrivate {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.Private;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.Private);
			}
		}

		public bool IsFamilyAndAssembly {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamANDAssem; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.FamANDAssem;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.FamANDAssem);
			}
		}

		public bool IsAssembly {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assem; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.Assem;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.Assem);
			}
		}

		public bool IsFamily {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.Family;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.Family);
			}
		}

		public bool IsFamilyOrAssembly {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.FamORAssem;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.FamORAssem);
			}
		}

		public bool IsPublic {
			get { return (attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.MemberAccessMask;
					attributes |= MethodAttributes.Public;
				} else
					attributes &= ~(MethodAttributes.MemberAccessMask & MethodAttributes.Public);
			}
		}

		public bool IsStatic {
			get { return (attributes & MethodAttributes.Static) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.Static;
				else
					attributes &= ~MethodAttributes.Static;
			}
		}

		public bool IsFinal {
			get { return (attributes & MethodAttributes.Final) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.Final;
				else
					attributes &= ~MethodAttributes.Final;
			}
		}

		public bool IsVirtual {
			get { return (attributes & MethodAttributes.Virtual) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.Virtual;
				else
					attributes &= ~MethodAttributes.Virtual;
			}
		}

		public bool IsHideBySig {
			get { return (attributes & MethodAttributes.HideBySig) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.HideBySig;
				else
					attributes &= ~MethodAttributes.HideBySig;
			}
		}

		public bool IsReuseSlot {
			get { return (attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.VtableLayoutMask;
					attributes |= MethodAttributes.ReuseSlot;
				} else
					attributes &= ~(MethodAttributes.VtableLayoutMask & MethodAttributes.ReuseSlot);
			}
		}

		public bool IsNewSlot {
			get { return (attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot; }
			set {
				if (value) {
					attributes &= ~MethodAttributes.VtableLayoutMask;
					attributes |= MethodAttributes.NewSlot;
				} else
					attributes &= ~(MethodAttributes.VtableLayoutMask & MethodAttributes.NewSlot);
			}
		}

		public bool IsCheckAccessOnOverride {
			get { return (attributes & MethodAttributes.CheckAccessOnOverride) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.CheckAccessOnOverride;
				else
					attributes &= ~MethodAttributes.CheckAccessOnOverride;
			}
		}

		public bool IsAbstract {
			get { return (attributes & MethodAttributes.Abstract) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.Abstract;
				else
					attributes &= ~MethodAttributes.Abstract;
			}
		}

		public bool IsSpecialName {
			get { return (attributes & MethodAttributes.SpecialName) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.SpecialName;
				else
					attributes &= ~MethodAttributes.SpecialName;
			}
		}

		public bool IsPInvokeImpl {
			get { return (attributes & MethodAttributes.PInvokeImpl) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.PInvokeImpl;
				else
					attributes &= ~MethodAttributes.PInvokeImpl;
			}
		}

		public bool IsUnmanagedExport {
			get { return (attributes & MethodAttributes.UnmanagedExport) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.UnmanagedExport;
				else
					attributes &= ~MethodAttributes.UnmanagedExport;
			}
		}

		public bool IsRuntimeSpecialName {
			get { return (attributes & MethodAttributes.RTSpecialName) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.RTSpecialName;
				else
					attributes &= ~MethodAttributes.RTSpecialName;
			}
		}

		public bool HasSecurity {
			get { return (attributes & MethodAttributes.HasSecurity) != 0; }
			set {
				if (value)
					attributes |= MethodAttributes.HasSecurity;
				else
					attributes &= ~MethodAttributes.HasSecurity;
			}
		}

		#endregion

		#region MethodImplAttributes

		public bool IsIL {
			get { return (impl_attrs & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.IL; }
			set {
				if (value) {
					impl_attrs &= ~MethodImplAttributes.CodeTypeMask;
					impl_attrs |= MethodImplAttributes.IL;
				} else
					impl_attrs &= ~(MethodImplAttributes.CodeTypeMask & MethodImplAttributes.IL);
			}
		}

		public bool IsNative {
			get { return (impl_attrs & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.Native; }
			set {
				if (value) {
					impl_attrs &= ~MethodImplAttributes.CodeTypeMask;
					impl_attrs |= MethodImplAttributes.Native;
				} else
					impl_attrs &= ~(MethodImplAttributes.CodeTypeMask & MethodImplAttributes.Native);
			}
		}

		public bool IsRuntime {
			get { return (impl_attrs & MethodImplAttributes.CodeTypeMask) == MethodImplAttributes.Runtime; }
			set {
				if (value) {
					impl_attrs &= ~MethodImplAttributes.CodeTypeMask;
					impl_attrs |= MethodImplAttributes.Runtime;
				} else
					impl_attrs &= ~(MethodImplAttributes.CodeTypeMask & MethodImplAttributes.Runtime);
			}
		}

		public bool IsUnmanaged {
			get { return (impl_attrs & MethodImplAttributes.ManagedMask) == MethodImplAttributes.Unmanaged; }
			set {
				if (value) {
					impl_attrs &= ~MethodImplAttributes.ManagedMask;
					impl_attrs |= MethodImplAttributes.Unmanaged;
				} else
					impl_attrs &= ~(MethodImplAttributes.ManagedMask & MethodImplAttributes.Unmanaged);
			}
		}

		public bool IsManaged {
			get { return (impl_attrs & MethodImplAttributes.ManagedMask) == MethodImplAttributes.Managed; }
			set {
				if (value) {
					impl_attrs &= ~MethodImplAttributes.ManagedMask;
					impl_attrs |= MethodImplAttributes.Managed;
				} else
					impl_attrs &= ~(MethodImplAttributes.ManagedMask & MethodImplAttributes.Managed);
			}
		}

		public bool IsForwardRef {
			get { return (impl_attrs & MethodImplAttributes.ForwardRef) != 0; }
			set {
				if (value)
					impl_attrs |= MethodImplAttributes.ForwardRef;
				else
					impl_attrs &= ~MethodImplAttributes.ForwardRef;
			}
		}

		public bool IsPreserveSig {
			get { return (impl_attrs & MethodImplAttributes.PreserveSig) != 0; }
			set {
				if (value)
					impl_attrs |= MethodImplAttributes.PreserveSig;
				else
					impl_attrs &= ~MethodImplAttributes.PreserveSig;
			}
		}

		public bool IsInternalCall {
			get { return (impl_attrs & MethodImplAttributes.InternalCall) != 0; }
			set {
				if (value)
					impl_attrs |= MethodImplAttributes.InternalCall;
				else
					impl_attrs &= ~MethodImplAttributes.InternalCall;
			}
		}

		public bool IsSynchronized {
			get { return (impl_attrs & MethodImplAttributes.Synchronized) != 0; }
			set {
				if (value)
					impl_attrs |= MethodImplAttributes.Synchronized;
				else
					impl_attrs &= ~MethodImplAttributes.Synchronized;
			}
		}

		public bool NoInlining {
			get { return (impl_attrs & MethodImplAttributes.NoInlining) != 0; }
			set {
				if (value)
					impl_attrs |= MethodImplAttributes.NoInlining;
				else
					impl_attrs &= ~MethodImplAttributes.NoInlining;
			}
		}

		#endregion

		#region MethodSemanticsAttributes
		public bool IsSetter {
			get { return (sem_attrs & MethodSemanticsAttributes.Setter) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.Setter;
				else
					sem_attrs &= ~MethodSemanticsAttributes.Setter;
			}
		}

		public bool IsGetter {
			get { return (sem_attrs & MethodSemanticsAttributes.Getter) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.Getter;
				else
					sem_attrs &= ~MethodSemanticsAttributes.Getter;
			}
		}

		public bool IsOther {
			get { return (sem_attrs & MethodSemanticsAttributes.Other) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.Other;
				else
					sem_attrs &= ~MethodSemanticsAttributes.Other;
			}
		}

		public bool IsAddOn {
			get { return (sem_attrs & MethodSemanticsAttributes.AddOn) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.AddOn;
				else
					sem_attrs &= ~MethodSemanticsAttributes.AddOn;
			}
		}

		public bool IsRemoveOn {
			get { return (sem_attrs & MethodSemanticsAttributes.RemoveOn) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.RemoveOn;
				else
					sem_attrs &= ~MethodSemanticsAttributes.RemoveOn;
			}
		}

		public bool IsFire {
			get { return (sem_attrs & MethodSemanticsAttributes.Fire) != 0; }
			set {
				if (value)
					sem_attrs |= MethodSemanticsAttributes.Fire;
				else
					sem_attrs &= ~MethodSemanticsAttributes.Fire;
			}
		}

		#endregion

		public new TypeDefinition DeclaringType {
			get { return (TypeDefinition) base.DeclaringType; }
			set { base.DeclaringType = value; }
		}

		public bool IsConstructor {
			get {
				return this.IsRuntimeSpecialName && this.IsSpecialName &&
					(this.Name == ".cctor" || this.Name == ".ctor");
			}
		}

		public override bool IsDefinition {
			get { return true; }
		}

		internal MethodDefinition ()
		{
		}

		public override MethodDefinition Resolve ()
		{
			return this;
		}
	}
}
